/**
 * @file lcd.c
 * @brief 提供在LCD屏幕上进行基本绘图操作的函数集合
 * @details 该文件包含用于在特定LCD屏幕（800x480）上绘制点、填充颜色、
 * 显示BMP/JPEG图像以及渲染字体的函数。
 * 依赖于外部定义的 'lcd.h' 和 'jpeglib.h'。
 * @version 1.0
 * @date 2025-07-05
 */
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#include <stdlib.h>
#include "lcd.h"
#include "jpeglib.h"
#include <sys/types.h>
//#include <dirent.h>
#include <string.h>

/**
 * @brief LCD屏幕的内存映射指针
 * @details 该指针指向通过 mmap 映射到用户空间的LCD帧缓冲区的起始地址。
 * 对该指针的操作将直接改变屏幕上显示的像素。
 */
unsigned int *plcd;

/**
 * @brief 在LCD屏幕的指定坐标绘制一个像素点
 * @param x     像素点的X坐标 (0-799)
 * @param y     像素点的Y坐标 (0-479)
 * @param color 像素点的颜色 (32位ARGB格式, 如 0x00RRGGBB)
 */
void draw_point(int x, int y, int color)
{
    // 边界检查，确保绘制坐标在屏幕有效范围内
    if ((x >= 0 && x < 800) && (y >= 0 && y < 480))
    {
        // 直接操作内存映射地址来绘制像素点
        // 计算公式: 起始地址 + 行偏移(y * 800) + 列偏移(x)
        *(plcd + y * 800 + x) = color;
    }
}

/**
 * @brief 使用单一颜色填充整个LCD屏幕
 * @param color 用于填充的颜色 (32位ARGB格式)
 */
void brush_lcd(int color)
{
    int x, y;
    // 遍历屏幕上的每一个像素点
    for (y = 0; y < 480; y++)
    {
        for (x = 0; x < 800; x++)
        {
            draw_point(x, y, color);
        }
    }
}

/**
 * @brief 从内存缓冲区绘制一张原始图像
 * @param image 包含24位RGB像素数据的缓冲区
 * @param wide  图像的宽度
 * @param high  图像的高度
 * @param x1    绘制区域左上角的X坐标
 * @param y1    绘制区域左上角的Y坐标
 */
void draw_image(const unsigned char image[], int wide, int high, int x1, int y1)
{
    unsigned int k = 0;
    unsigned int x, y;

    // 从图像的最后一行开始向上绘制，这通常是为了适配原始图像数据（如BMP）的存储方式
    for (y = high; y > 0; y--)
    {
        for (x = 0; x < wide; x++)
        {
            // 从缓冲区中读取3个字节(B, G, R)并合成为一个32位的颜色值
            // 假设image[]中的颜色顺序是B, G, R
            int color = (image[k] | (image[k + 1] << 8) | (image[k + 2] << 16));
            // 使用偏移量控制绘制位置
            draw_point(x1 + x, y1 + y - 1, color);
            k += 3; // 指向下一个像素的B字节
        }
    }
}

/**
 * @brief 在LCD的指定位置绘制一个BMP图像
 * @param bmpfile   BMP图像文件的路径
 * @param x1        绘制区域左上角的X坐标
 * @param y1        绘制区域左上角的Y坐标
 * @return int      成功返回0, 失败返回-1
 */
int draw_bmp(const char *bmpfile, int x1, int y1)
{
    unsigned char buf[4]; // 临时缓冲区，用于读取文件头信息

    // 以只读方式打开BMP文件
    int fd = open(bmpfile, O_RDONLY);
    if (fd < 0)
    {
        perror("open bmp file failed");
        return -1;
    }

    // 1. 验证文件类型：读取文件头的前两个字节，判断是否为 'B' 'M'
    read(fd, buf, 2);
    if (buf[0] != 'B' || buf[1] != 'M')
    {
        fprintf(stderr, "Error: Not a BMP file.\n");
        close(fd);
        return -1;
    }

    // 2. 读取图像宽度：跳转到偏移量 0x12 处读取4个字节
    lseek(fd, 0x12, SEEK_SET);
    read(fd, buf, 4);
    int width = (buf[3] << 24) | (buf[2] << 16) | (buf[1] << 8) | buf[0];

    // 3. 读取图像高度：跳转到偏移量 0x16 处读取4个字节
    lseek(fd, 0x16, SEEK_SET);
    read(fd, buf, 4);
    int hight = (buf[3] << 24) | (buf[2] << 16) | (buf[1] << 8) | buf[0];

    // 4. 读取色深（每个像素的位数）：跳转到偏移量 0x1c 处读取2个字节
    lseek(fd, 0x1c, SEEK_SET);
    read(fd, buf, 2);
    int depth = (buf[1] << 8) | buf[0];

    // 5. 分配内存以存储像素数据
    // 计算所需内存大小：宽 * 高 * (色深 / 8)
    unsigned char *image = malloc(width * hight * depth / 8);
    if (image == NULL) {
        perror("malloc for bmp image failed");
        close(fd);
        return -1;
    }

    // 6. 读取像素数据：BMP文件头通常为54字节，之后是像素数据
    lseek(fd, 54, SEEK_SET);
    read(fd, image, width * hight * depth / 8);

    // 7. 调用通用绘图函数将缓冲区数据显示到屏幕上
    draw_image(image, width, hight, x1, y1);

    // 8. 清理资源
    free(image); // 释放内存
    close(fd);   // 关闭文件
    return 0;
}

/**
 * @brief 在指定位置绘制一个8x16的单色字符或图案
 * @param x0    字符左上角的X坐标
 * @param y0    字符左上角的Y坐标
 * @param buf   包含16字节的点阵数据 (16行，每行1字节)
 * @param color 字符的颜色
 */
void draw_8x16(int x0, int y0, unsigned char buf[], int color)
{
    int i, j;
    // 遍历16行
    for (j = 0; j < 16; j++)
    {
        // 遍历每行的8个位 (即8个像素)
        for (i = 0; i < 8; i++)
        {
            // 检查buf[j]的第(7-i)位是否为1
            // (1 << (7-i)) 生成一个掩码，如 10000000, 01000000, ...
            if (buf[j] & (1 << (7 - i)))
            {
                // 如果该位为1，则在此位置绘制一个点
                draw_point(x0 + i, y0 + j, color);
            }
        }
    }
}

/**
 * @brief 在指定位置绘制一个16x16的单色字符或图案 (如汉字)
 * @param x0    字符左上角的X坐标
 * @param y0    字符左上角的Y坐标
 * @param buf   包含32字节的点阵数据 (16行，每行2字节)
 * @param color 字符的颜色
 */
void draw_16x16(int x0, int y0, unsigned char buf[], int color)
{
    int i, j;
    // 遍历16行
    for (j = 0; j < 16; j++)
    {
        // 处理每行的前8个像素 (由第一个字节 buf[j*2] 控制)
        for (i = 0; i < 8; i++)
        {
            if (buf[j * 2] & (1 << (7 - i)))
            {
                draw_point(x0 + i, y0 + j, color);
            }
        }
        // 处理每行的后8个像素 (由第二个字节 buf[j*2+1] 控制)
        for (i = 0; i < 8; i++)
        {
            if (buf[j * 2 + 1] & (1 << (7 - i)))
            {
                // 注意x坐标需要偏移8
                draw_point(x0 + i + 8, y0 + j, color);
            }
        }
    }
}

/**
 * @brief 使用libjpeg解码并绘制一个JPEG图像
 * @param jpegfile JPEG图像文件的路径
 * @param x0       绘制区域左上角的X坐标
 * @param y0       绘制区域左上角的Y坐标
 */
void draw_jpeg(const char *jpegfile, int x0, int y0)
{
    // 1. 初始化libjpeg对象
    struct jpeg_decompress_struct dinfo; // 声明解压对象
    struct jpeg_error_mgr jerr;          // 声明错误信息对象
    dinfo.err = jpeg_std_error(&jerr);   // 初始化错误对象
    jpeg_create_decompress(&dinfo);      // 初始化解压对象

    // 2. 指定输入文件
    FILE *infile;
    infile = fopen(jpegfile, "r");
    if (infile == NULL)
    {
        perror("fopen jpeg file failed");
        return;
    }
    jpeg_stdio_src(&dinfo, infile); // 设置解压源为标准IO文件

    // 3. 读取JPEG文件头信息
    jpeg_read_header(&dinfo, TRUE);

    // 4. 启动解压过程
    jpeg_start_decompress(&dinfo);

    // 从解压对象中获取图像信息
    int width = dinfo.output_width;
    int height = dinfo.output_height;
    int components = dinfo.output_components; // 通常是3 (RGB)
    
    // 5. 分配内存用于存储一行解压后的像素数据
    unsigned char *buffer = malloc(width * components);

    // 6. 逐行读取、解压和绘制
    int y = 0;
    while (dinfo.output_scanline < height)
    {
        // 读取一行扫描线数据到buffer中
        jpeg_read_scanlines(&dinfo, &buffer, 1);

        // 遍历这一行中的所有像素
        for (int x = 0, k = 0; x < width; x++)
        {
            // 将buffer中的RGB数据合成为32位颜色值
            int color = (buffer[k] << 16) | (buffer[k + 1] << 8) | (buffer[k + 2]);
            // 在屏幕上绘制这个点
            draw_point(x0 + x, y0 + y, color); // y0+y, 不是 y0+y-1,因为是逐行向下读取的
            k += 3; // 移动到下一个像素的R分量
        }
        y++;
    }

    // 7. 清理资源
    free(buffer);
    jpeg_finish_decompress(&dinfo);
    jpeg_destroy_decompress(&dinfo);
    fclose(infile);
}


/**
 * @brief 使用libjpeg解码JPEG，并按比例缩放后绘制到指定的“视口”内
 * @param jpegfile      图片路径
 * @param scale         缩放比例 (例如: 0.5表示缩小一半, 2.0表示放大一倍)
 * @param pan_offset_x  图像在视口内的水平平移偏移量
 * @param pan_offset_y  图像在视口内的垂直平移偏移量
 * @param viewport_x    视口左上角X坐标
 * @param viewport_y    视口左上角Y坐标
 * @param viewport_w    视口宽度
 * @param viewport_h    视口高度
 */
void draw_jpeg_with_scale(const char *jpegfile, double scale, int pan_offset_x, int pan_offset_y, int viewport_x, int viewport_y, int viewport_w, int viewport_h) {
    // 1. 初始化libjpeg并打开文件
    struct jpeg_decompress_struct cinfo;
    struct jpeg_error_mgr jerr;
    FILE *infile = fopen(jpegfile, "rb");
    if (infile == NULL) return;

    cinfo.err = jpeg_std_error(&jerr);
    jpeg_create_decompress(&cinfo);
    jpeg_stdio_src(&cinfo, infile);
    jpeg_read_header(&cinfo, TRUE);
    jpeg_start_decompress(&cinfo);

    int src_width = cinfo.output_width;
    int src_height = cinfo.output_height;
    int num_components = cinfo.output_components;

    // 2. 分配足够内存一次性读入整个解压后的图像
    // 注意：这种方法对于大图会消耗大量内存，但便于进行缩放计算
    unsigned char *image_buffer = (unsigned char *)malloc(src_width * src_height * num_components);
    if (!image_buffer) {
        jpeg_destroy_decompress(&cinfo);
        fclose(infile);
        return;
    }

    // 3. 逐行读取并将解压数据存入image_buffer
    while (cinfo.output_scanline < cinfo.output_height) {
        unsigned char *row_ptr = image_buffer + cinfo.output_scanline * src_width * num_components;
        jpeg_read_scanlines(&cinfo, &row_ptr, 1);
    }
    
    // 4. 计算缩放后的尺寸和绘制起始点
    int scaled_width = (int)(src_width * scale);
    int scaled_height = (int)(src_height * scale);
    // 让图片在视口内居中, 并应用手动平移偏移量
    int start_x = viewport_x + (viewport_w - scaled_width) / 2 + pan_offset_x;
    int start_y = viewport_y + (viewport_h - scaled_height) / 2 + pan_offset_y;

    // 5. 遍历目标绘制区域（缩放后的尺寸），进行绘制
    for (int y = 0; y < scaled_height; y++) {
        for (int x = 0; x < scaled_width; x++) {
            // 计算当前像素在屏幕上的绝对坐标
            int draw_x = start_x + x;
            int draw_y = start_y + y;

            // 裁剪：只绘制在视口范围内的像素
            if (draw_x >= viewport_x && draw_x < viewport_x + viewport_w &&
                draw_y >= viewport_y && draw_y < viewport_y + viewport_h) {
                
                // 6. 核心：通过反向计算，找到目标像素(x,y)对应的源图像像素(src_x, src_y)
                // 这是最简单的"最近邻"插值算法
                int src_y = (int)(y / scale);
                int src_x = (int)(x / scale);

                // 从源图像缓冲区中取出对应像素的颜色值
                unsigned char *pixel_ptr = image_buffer + (src_y * src_width + src_x) * num_components;
                // 假设源数据是RGB顺序
                int color = (pixel_ptr[0] << 16) | (pixel_ptr[1] << 8) | pixel_ptr[2];
                draw_point(draw_x, draw_y, color);
            }
        }
    }

    // 7. 释放所有资源
    free(image_buffer);
    jpeg_finish_decompress(&cinfo);
    jpeg_destroy_decompress(&cinfo);
    fclose(infile);
}

/**
 * @brief 读取BMP文件，并按比例缩放、居中绘制到指定“视口”内
 * @details 此函数能正确处理BMP的BGR颜色顺序和垂直翻转的像素存储方式。
 * @param bmpfile       BMP图像文件的路径
 * @param scale         缩放比例
 * @param viewport_x    视口左上角X坐标
 * @param viewport_y    视口左上角Y坐标
 * @param viewport_w    视口宽度
 * @param viewport_h    视口高度
 * @param pan_offset_x  图像在视口内的水平平移偏移量
 * @param pan_offset_y  图像在视口内的垂直平移偏移量
 */
void draw_bmp_with_scale(const char *bmpfile, double scale, int pan_offset_x, int pan_offset_y, int viewport_x, int viewport_y, int viewport_w, int viewport_h) {
    // 1. 打开文件并读取54字节的BMP文件头
    int fd = open(bmpfile, O_RDONLY);
    if (fd < 0) return;

    unsigned char header[54];
    if (read(fd, header, 54) != 54 || header[0] != 'B' || header[1] != 'M') {
        close(fd);
        return;
    }

    // 2. 从文件头中解析出图像的宽度、高度和色深
    int src_width = *(int*)&header[18];
    int src_height = *(int*)&header[22];
    short bpp = *(short*)&header[28]; // bits per pixel
    if (bpp != 24 && bpp != 32) { // 只支持24位和32位BMP
        close(fd);
        return;
    }
    
    // 3. 一次性将所有像素数据读入内存
    int bytes_per_pixel = bpp / 8;
    unsigned char *image_buffer = (unsigned char *)malloc(src_width * src_height * bytes_per_pixel);
    if (!image_buffer) {
        close(fd);
        return;
    }
    
    // 定位到像素数据区的起始位置（由文件头偏移量10的字段指定）并读取
    lseek(fd, *(int*)&header[10], SEEK_SET);
    read(fd, image_buffer, src_width * src_height * bytes_per_pixel);
    close(fd); // 数据已读入内存，可以关闭文件

    // 4. 计算缩放后的尺寸和绘制起始点（同JPEG函数）
    int scaled_width = (int)(src_width * scale);
    int scaled_height = (int)(src_height * scale);
    int start_x = viewport_x + (viewport_w - scaled_width) / 2 + pan_offset_x;
    int start_y = viewport_y + (viewport_h - scaled_height) / 2 + pan_offset_y;

    // 5. 遍历目标绘制区域进行绘制
    for (int y = 0; y < scaled_height; y++) {
        for (int x = 0; x < scaled_width; x++) {
            int draw_x = start_x + x;
            int draw_y = start_y + y;
            
            // 裁剪：只绘制视口内的部分
            if (draw_x >= viewport_x && draw_x < viewport_x + viewport_w &&
                draw_y >= viewport_y && draw_y < viewport_y + viewport_h) {
                
                // 6. 核心：通过反向计算找到源像素
                // **关键点1**: BMP图像数据是倒着存的（第一行在文件末尾），所以需要垂直翻转坐标。
                // src_y = (总高 - 1) - 目标y对应的源y
                int src_y = src_height - 1 - (int)(y / scale);
                int src_x = (int)(x / scale);

                // 从缓冲区中定位到像素数据
                unsigned char *pixel_ptr = image_buffer + (src_y * src_width + src_x) * bytes_per_pixel;
                // **关键点2**: BMP的像素颜色顺序是 BGR，需要转换为屏幕通常使用的RGB格式。
                int color = (pixel_ptr[2] << 16) | (pixel_ptr[1] << 8) | pixel_ptr[0]; // R G B
                draw_point(draw_x, draw_y, color);
            }
        }
    }

    // 7. 释放内存
    free(image_buffer);
}


/**
 * @brief 在指定位置绘制一个实心矩形
 * @param x         矩形左上角的X坐标
 * @param y         矩形左上角的Y坐标
 * @param width     矩形的宽度
 * @param height    矩形的高度
 * @param color     矩形的填充颜色
 */
void lcd_draw_rect(int x, int y, int width, int height, int color)
{
    // 边界检查，防止绘制超出屏幕范围
    if (x < 0 || y < 0 || x + width > 800 || y + height > 480)
    {
        printf("Error: Drawing position is out of screen boundary.\n");
        return;
    }

    int i, j;
    // 遍历矩形区域内的每一个像素点
    for (i = y; i < y + height; i++)
    {
        for (j = x; j < x + width; j++)
        {
            draw_point(j, i, color);
        }
    }
}